﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ParallelMath.Library.AbstractDependencies;
using ParallelMath.Library.ConcreteDependencies;

namespace ParallelMath.Library
{
    public static class MatrixOperations
    {

        static MatrixOperations()
        {
            ThreadsCount=new ProcessorsBasedThreadsCount();
        }

        public static IThreadsCount ThreadsCount { get; set; }

        public static double[,] GenerateRandom(long dimension1Size, long dimension2Size, double minValue=0, double maxValue=1)
        {
            Random r=new Random();
            double[,] generatedMatrix=new double[dimension1Size,dimension2Size];

            for (int i = 0; i < dimension1Size; i++)
            {
                for (int j = 0; j < dimension2Size; j++)
                {
                    generatedMatrix[i,j] = r.NextDouble()*(maxValue - minValue)+minValue;
                }
            }

            return generatedMatrix;
        }

        public static double[,] Add(this double[,] matrix1, double[,] matrix2, bool executeInOneThread = false)
        {
            if (matrix1.GetLength(0) != matrix2.GetLength(0) || matrix1.GetLength(1) != matrix2.GetLength(1))
                throw new ArgumentException("The length of dimensions must be equal");

            if (Environment.ProcessorCount == 1 || executeInOneThread)
                return matrix1.AddInOneThread(matrix2);

            double[,] result = new double[matrix1.GetLength(0),matrix1.GetLength(1)];

            long elementsInOnePart = result.LongLength/ThreadsCount.ThreadsCount;
            long extraElementsInFirstPart = result.LongLength%ThreadsCount.ThreadsCount;

            long startPosition = 0;

            Task[] tasks = new Task[ThreadsCount.ThreadsCount];
            for (int taskNum = 0; taskNum < ThreadsCount.ThreadsCount; taskNum++)
            {
                long elements = elementsInOnePart;
                if (taskNum == 0)
                {
                    elements += extraElementsInFirstPart;
                }
                long position = startPosition;
                Task task = Task.Factory.StartNew(() =>
                                                      {
                                                          long index1 = 0;
                                                          long index2 = 0;

                                                          matrix1.ConvertIndexIntoArrayDimensions(position,
                                                                                                  ref index1,
                                                                                                  ref index2);
                                                          int elementsInCurrentSession = 0;

                                                          long dimension1Len = matrix1.GetLongLength(0);
                                                          long dimension2Len = matrix1.GetLongLength(1);

                                                          for (long i = index1; i < dimension1Len; i++)
                                                          {
                                                              long j = i == index1 ? index2 : 0;
                                                              for (; j < dimension2Len; j++)
                                                              {
                                                                  result[i, j] = matrix1[i, j] + matrix2[i, j];
                                                                  if (++elementsInCurrentSession == elements)
                                                                      break;
                                                              }
                                                              if (elementsInCurrentSession == elements)
                                                                  break;
                                                          }
                                                      });


                tasks[taskNum] = task;
                startPosition += elements;
            }

            Task.WaitAll(tasks);

            return result;
        }

        private static double[,] AddInOneThread(this double[,] matrix1, double[,] matrix2)
        {
            int dimension1 = matrix1.GetLength(0);
            int dimension2 = matrix1.GetLength(1);

            double[,] result = new double[dimension1, dimension2];

            for (long i = 0; i < dimension1; i++)
            {
                for (long j = 0; j < dimension2; j++)
                {
                    result[i, j] = matrix1[i, j] + matrix2[i, j];
                }
            }

            return result;
        }

        public static bool ConvertIndexIntoArrayDimensions(this double[,] matrix, long index, ref long d1, ref long d2)
        {
            if(index>matrix.LongLength)
            {
                return false;
            }
            d1 = index/matrix.GetLength(1);
            d2 = index%matrix.GetLength(1);

            return true;
        }


    }
}
